/*
 * ProgressPanel.java
 *
 * Created on 27-Feb-2013, 9:06:20 AM
 */
package swingExtended.progressPanel;

import java.beans.PropertyChangeEvent;
import swingExtended.customTextPane.SimpleTextPane;
import action.unary.UnaryAction;
import java.awt.Color;
import java.awt.EventQueue;
import java.beans.PropertyChangeListener;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.TimerTask;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
import list.functions.ListFunctions;
import processorIO.FileIO;
import stringFunctions.StringFunctions;
import swingExtended.EstimatingProgressBar;
import swingExtended.SwingExtended;
import swingExtended.customTextPane.CustomTextPaneListener;
import swingExtended.customTextPane.DynamicFontTextPane;
import swingExtended.customTextPane.ErrorTextPane;
import time.TimeUtil;
import time.Timer;

/**
 *
 * @author Shimu
 */
public class ProgressPanel extends javax.swing.JPanel {

    public static final String DEFAULT_ENCODING = "UTF-8";

    public class MessageType<T extends SimpleTextPane> {

        private static final String ERROR_PROMPT =
                "The operation will continue, but will not be able to write output to a file. Continue anyways?\n";
        private final String URL;
        private final T TEXTPANE;

        private MessageType(String url, Color color, T textPane) {
            this.URL = url;
            this.TEXTPANE = textPane;
            textPane.setColor(color);
        }

        public boolean setUrl() {
            assert (EventQueue.isDispatchThread());

            try {
                TEXTPANE.setOutputUrl(FileIO.getUniqueName(URL));

            } catch (UnsupportedEncodingException ex) {
                // Should not happen
                System.err.printf("Unsupported Encoding: %s\n", DEFAULT_ENCODING);
                if (!SwingExtended.openConfirmWindow(ProgressPanel.this, String.format(
                        "This computer does not support the encoding: %s\n" + ERROR_PROMPT,
                        DEFAULT_ENCODING), JOptionPane.YES_NO_OPTION)) {
                    return false;
                }

            } catch (FileNotFoundException ex) {
                if (!SwingExtended.openConfirmWindow(ProgressPanel.this, String.format(
                        "File: %s is not found!\n" + ERROR_PROMPT, URL),
                        JOptionPane.YES_NO_OPTION)) {
                    return false;
                }
            } catch (IOException ex) {
                if (!SwingExtended.openConfirmWindow(ProgressPanel.this, String.format(
                        "An error occurred while opening the file\n" + ERROR_PROMPT, URL),
                        JOptionPane.YES_NO_OPTION)) {
                    return false;
                }
            }
            return true;
        }

        public void close() {
            assert (EventQueue.isDispatchThread());

            // Close the writers
            try {
                TEXTPANE.closeWriter();

            } catch (IOException ex) {
                SwingExtended.openWarningWindow(ProgressPanel.this,
                        String.format("%s was not closed properly!", URL));
            }
        }
    }
    
    //public static final String DEFAULT_ENCODING = "fail";
    /** The URL of the file to write to. */
    public final MessageType OUTPUT, STATUS, ERROR_TYPE;
    private final List<ProgressPanelWorkerFactory> WORKER_FACTORY =
            new ArrayList<>();
    private final List<ProgressPanelListener> LISTENERS = 
            new ArrayList<>();
    private final List<SwingWorker> WORKERS = new LinkedList<>();
    
    /** Creates new form ProgressPanel */
    public ProgressPanel() {
        initComponents();

        OUTPUT = new MessageType("output/output.tmp", Color.BLACK, textPaneOutput);
        STATUS = new MessageType(null, Color.BLUE, textPaneStatus);
        ERROR_TYPE = new MessageType("output/error.tmp", Color.RED, textPaneError);

        // Initially disable the stop button
        this.buttonStop.setEnabled(false);
        this.buttonStop.validate();

        // A listener for each of the text pane so whenever anything is
        // written to any of them, the same text is also written to the "All"
        // text pane
        CustomTextPaneListener listener = new CustomTextPaneListener() {

            @Override
            public void onAddText(String text, SimpleTextPane eventEmitter) {
                textPaneAll.setColor(eventEmitter.getColor());
                textPaneAll.print(text);
            }
        };
        textPaneStatus.addListener(listener);
        textPaneOutput.addListener(listener);
        textPaneError.addListener(listener);

        // Code for updating the elapsed time String

        //tabbedPane.setForegroundAt(1, COLOR_STATUS);
        //tabbedPane.setBackgroundAt(2, COLOR_OUTPUT);
        //tabbedPane.setBackgroundAt(3, COLOR_ERROR);
    }

    public EstimatingProgressBar getProgressBar() {
        return this.estimatingProgressBar;
    }

    /**
     * 
     * @return 
     */
    public DynamicFontTextPane getAllTextPane() {
        assert (EventQueue.isDispatchThread());

        return this.textPaneAll;
    }

    public ErrorTextPane getErrorTextPane() {
        assert (EventQueue.isDispatchThread());

        return this.textPaneError;
    }

    public SimpleTextPane getStatusTextPane() {
        assert (EventQueue.isDispatchThread());

        return this.textPaneStatus;
    }

    public DynamicFontTextPane getOutputTextPane() {
        assert (EventQueue.isDispatchThread());

        return this.textPaneOutput;
    }

    public void setStatusText(String text) {
        labelCurrentStatus.setText(text);
    }
    
    public void setSpeedText(String text) {
        labelSpeed.setText(text);
    }
    /**
     * Add a listener to this ProgressPanel.
     * @param listener 
     */
    public void addWorkerFactory(ProgressPanelWorkerFactory listener) {
        this.WORKER_FACTORY.add(listener);
    }
    
    public void addListener(ProgressPanelListener listener) {
        this.LISTENERS.add(listener);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        estimatingProgressBar = new swingExtended.EstimatingProgressBar();
        buttonClear = new javax.swing.JButton();
        buttonStart = new javax.swing.JButton();
        buttonStop = new javax.swing.JButton();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        labelElapsedTime = new javax.swing.JLabel();
        tabbedPane = new javax.swing.JTabbedPane();
        scrollPaneAll = new javax.swing.JScrollPane();
        textPaneAll = new swingExtended.customTextPane.DynamicFontTextPane();
        scrollPaneStatus = new javax.swing.JScrollPane();
        textPaneStatus = new swingExtended.customTextPane.SimpleTextPane();
        scrollPaneOutput = new javax.swing.JScrollPane();
        textPaneOutput = new swingExtended.customTextPane.DynamicFontTextPane();
        scrollPaneError = new javax.swing.JScrollPane();
        textPaneError = new swingExtended.customTextPane.ErrorTextPane();
        buttonClearAll = new javax.swing.JButton();
        jLabel3 = new javax.swing.JLabel();
        labelCurrentStatus = new javax.swing.JLabel();
        jLabel4 = new javax.swing.JLabel();
        labelSpeed = new javax.swing.JLabel();

        estimatingProgressBar.setToolTipText("The current progress of the operation (estimated time updated after each file operation)");

        buttonClear.setText("Clear");
        buttonClear.setToolTipText("Clears the selected tab.");
        buttonClear.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                buttonClearActionPerformed(evt);
            }
        });

        buttonStart.setText("Start");
        buttonStart.setToolTipText("Start the operation");
        buttonStart.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                buttonStartActionPerformed(evt);
            }
        });

        buttonStop.setText("Stop");
        buttonStop.setToolTipText("Stop the operation. Note it may take some time for the program stop.");
        buttonStop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                buttonStopActionPerformed(evt);
            }
        });

        jLabel1.setText("Progress");

        jLabel2.setText("Elapsed Time:");

        labelElapsedTime.setText("00:00:00");

        scrollPaneAll.setViewportView(textPaneAll);

        tabbedPane.addTab("All", null, scrollPaneAll, "All messages");

        scrollPaneStatus.setViewportView(textPaneStatus);

        tabbedPane.addTab("Status", null, scrollPaneStatus, "The current status of this program");

        scrollPaneOutput.setViewportView(textPaneOutput);

        tabbedPane.addTab("Output", null, scrollPaneOutput, "Any output that is written to a file");

        scrollPaneError.setViewportView(textPaneError);

        tabbedPane.addTab("Error", scrollPaneError);

        buttonClearAll.setText("Clear All");
        buttonClearAll.setToolTipText("Clears all the tabs");
        buttonClearAll.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                buttonClearAllActionPerformed(evt);
            }
        });

        jLabel3.setText("Processing:");

        labelCurrentStatus.setText("N/A");

        jLabel4.setText("Speed:");

        labelSpeed.setText("N/A");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(tabbedPane, javax.swing.GroupLayout.DEFAULT_SIZE, 563, Short.MAX_VALUE)
                        .addContainerGap())
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addComponent(buttonClear)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(buttonClearAll)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 311, Short.MAX_VALUE)
                        .addComponent(buttonStart)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(buttonStop)
                        .addContainerGap())
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                            .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                                .addComponent(jLabel3)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(labelCurrentStatus, javax.swing.GroupLayout.DEFAULT_SIZE, 454, Short.MAX_VALUE))
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(jLabel1)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(estimatingProgressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 349, Short.MAX_VALUE)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(jLabel2)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(labelElapsedTime)))
                        .addGap(58, 58, 58))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jLabel4)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(labelSpeed)
                        .addContainerGap(515, Short.MAX_VALUE))))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(estimatingProgressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel1)
                    .addComponent(jLabel2)
                    .addComponent(labelElapsedTime))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel3)
                    .addComponent(labelCurrentStatus))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 8, Short.MAX_VALUE)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel4)
                    .addComponent(labelSpeed))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(buttonClear)
                    .addComponent(buttonStart)
                    .addComponent(buttonStop)
                    .addComponent(buttonClearAll))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(tabbedPane, javax.swing.GroupLayout.PREFERRED_SIZE, 279, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap())
        );
    }// </editor-fold>//GEN-END:initComponents

    private void buttonClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonClearActionPerformed
        int index = this.tabbedPane.getSelectedIndex();
        switch (index) {
            case 0:
                textPaneAll.clearPane();
                break;
            case 1:
                textPaneStatus.clearPane();
                break;
            case 2:
                textPaneOutput.clearPane();
                break;
            case 3:
                textPaneError.clearPane();
                break;
            default:
                break;
        }
    }//GEN-LAST:event_buttonClearActionPerformed
    private int totalWorkers;

    /**
     * Should be called whenever a SwingWorker has finished
     * 
     * @return true if all workers have finished, false otherwise
     */
    private synchronized boolean workerFinished() {
        totalWorkers--;
        return (totalWorkers == 0);
    }

    private void start() {

        if (!OUTPUT.setUrl() || !ERROR_TYPE.setUrl()) {
            return;
        }

        this.buttonStart.setEnabled(false);
        this.buttonStop.setEnabled(true);
                
        this.estimatingProgressBar.start();
        
        // Updates the elapsed time String
        final java.util.Timer timeUpdater = new java.util.Timer();
        timeUpdater.scheduleAtFixedRate(new TimerTask() {
            private final Timer timer = new Timer(false);

            {
                timer.start();
            }                
                
            @Override
            public void run() {                
                labelElapsedTime.setText(
                        TimeUtil.getTimeString(timer.getElapsedTime()));
            }
        }, 0, 1000);
        WORKERS.clear();

        ListFunctions.forEach(new UnaryAction<ProgressPanelListener>(){
            @Override
            public void run(ProgressPanelListener listener) {
                listener.onStart(ProgressPanel.this);
            }
        }, LISTENERS);
        
        totalWorkers = WORKER_FACTORY.size();
        ListFunctions.forEach(new UnaryAction<ProgressPanelWorkerFactory>() {

            @Override
            public void run(ProgressPanelWorkerFactory factory) {
                final SwingWorker instance = factory.CreateWorker();
                instance.addPropertyChangeListener(new PropertyChangeListener() {

                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        String propertyName = evt.getPropertyName();
                        if (propertyName.equals("state")) {
                            SwingWorker.StateValue state =
                                    (SwingWorker.StateValue) evt.getNewValue();
                            if ((state == SwingWorker.StateValue.DONE)
                                    && workerFinished()) {
                                onFinish();
                                timeUpdater.cancel();
                            }
                        }
                    }
                });
                WORKERS.add(instance);
                instance.execute();
            }
        }, this.WORKER_FACTORY);
    }

    private void buttonStartActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonStartActionPerformed
        start();
    }//GEN-LAST:event_buttonStartActionPerformed

    /**
     * Called when all the Listeners have stopped.
     */
    private void onFinish() {        
        // Display the unsuported code points
        List<String> unsupported = textPaneAll.getUnsupportedCodePoints();
        if (!unsupported.isEmpty()) {
            String msg = String.format(
                    "The following characters (listed in hex-value) could not be displayed and was replaced by %s character:\n%s\n",
                    SimpleTextPane.REPLACEMENT_CHAR,
                    StringFunctions.insertSeparator(unsupported, "\n"));
            textPaneError.println(msg);
            SwingExtended.openWarningWindow(ProgressPanel.this, msg);
        }

        // Close the writers
        try {
            textPaneOutput.closeWriter();
            textPaneError.closeWriter();

        } catch (IOException ex) {
            SwingExtended.openWarningWindow(ProgressPanel.this,
                    "The output file was not closed properly!");
        }

        ListFunctions.forEach(new UnaryAction<ProgressPanelListener>(){

            @Override
            public void run(ProgressPanelListener listener) {
                listener.onStop(ProgressPanel.this);
            }
        }, LISTENERS);
        
        buttonStart.setEnabled(true);        
        if (buttonStop.isEnabled()) {
            // User did not press the stop button
            textPaneStatus.println("FINISHED");
            buttonStop.setEnabled(false);
        } else {
            textPaneStatus.println("PROCESS ABORTED");
        }
        this.estimatingProgressBar.finish();
    }

    private void buttonStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonStopActionPerformed
        this.buttonStop.setEnabled(false);
        textPaneStatus.println("Aborting operation, please wait ...");

        // Request stop for the listeners that did not stop
        ListFunctions.forEach(new UnaryAction<SwingWorker>() {

            @Override
            public void run(SwingWorker worker) {
                if (!worker.isDone()) {
                    worker.cancel(false);
                }
            }
        }, WORKERS);
    }//GEN-LAST:event_buttonStopActionPerformed

    private void buttonClearAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonClearAllActionPerformed
        textPaneAll.clearPane();
        textPaneOutput.clearPane();
        textPaneError.clearPane();
        textPaneStatus.clearPane();
    }//GEN-LAST:event_buttonClearAllActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton buttonClear;
    private javax.swing.JButton buttonClearAll;
    private javax.swing.JButton buttonStart;
    private javax.swing.JButton buttonStop;
    private swingExtended.EstimatingProgressBar estimatingProgressBar;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel labelCurrentStatus;
    private javax.swing.JLabel labelElapsedTime;
    private javax.swing.JLabel labelSpeed;
    private javax.swing.JScrollPane scrollPaneAll;
    private javax.swing.JScrollPane scrollPaneError;
    private javax.swing.JScrollPane scrollPaneOutput;
    private javax.swing.JScrollPane scrollPaneStatus;
    private javax.swing.JTabbedPane tabbedPane;
    private swingExtended.customTextPane.DynamicFontTextPane textPaneAll;
    private swingExtended.customTextPane.ErrorTextPane textPaneError;
    private swingExtended.customTextPane.DynamicFontTextPane textPaneOutput;
    private swingExtended.customTextPane.SimpleTextPane textPaneStatus;
    // End of variables declaration//GEN-END:variables
}
